{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：怎么实现只读属性？\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "答案\n",
    "使用 @property 定义只读属性。\n",
    "\n",
    "拓展\n",
    "要想知道property装饰器是如何实现只读属性的，我们就必须知道property这个内置函数是怎么工作的。先看看下面这段代码：\n",
    "\n",
    "class MyClass:\n",
    "    def __init__(self):\n",
    "        self._x = None\n",
    "        self._y = 1\n",
    "\n",
    "    def getx(self):\n",
    "        return self._x\n",
    "\n",
    "    def setx(self, value):\n",
    "        self._x = value\n",
    "\n",
    "    def delx(self):\n",
    "        del self._x\n",
    "\n",
    "    def gety(self):\n",
    "        return self._y\n",
    "\n",
    "    x = property(getx, setx, delx, \"I'm the 'x' property.\")\n",
    "    y = property(gety, doc=\"I'm the 'y' property.\")\n",
    "下面我们验证这个类的一些特性：\n",
    "\n",
    ">>> help(MyClass)\n",
    "这句语句会输出MyClass的帮助文档，然后你可以发现属性x和属性y的帮助文档就是调用property函数传入进去的。接下来，我们试试读写x属性：\n",
    "\n",
    ">>> c = MyClass()\n",
    ">>> c.x = 2\n",
    ">>> print(c.x)\n",
    "2\n",
    "上面这段演示说明x是可读可写的属性，那么属性y呢？\n",
    "\n",
    ">>> c.y = 3\n",
    "AttributeError: can't set attribute\n",
    "在写y属性的时候报错了，为什么呢？我们发现，x和y的property函数传入的参数有所不同。那么我们看看这个函数究竟需要哪些参数？它们是干什么用的？\n",
    "\n",
    "property(fget=None, fset=None, fdel=None, doc=None)\n",
    "这是官方文档给出的函数参数形式。property方法用于创建并返回一个属性对象。fget是获取属性值的函数。fset是用于设置属性值的函数。fdel是用于删除属性值的函数。并且doc为属性对象创建文档字符串。如果未给出doc的文档字符串，就会取fget方法中的文档字符串用做属性的文档字符串。\n",
    "\n",
    "这就不难发现，我们在设置y属性的时候并没有设置给y属性设置新值的函数，那么它就不支持写操作了。那么，y就是一个可读属性。\n",
    "\n",
    "另外，我们要知道，如果c是MyClass的实例，x是一个属性对象。属性对象有三个方法getter()，setter()和deleter()，它们分别由fget，fset，fdel指定。c.x就相当于调用getter，c.x = value相当于调用setter，del c.x相当于调用deleter。\n",
    "\n",
    "下面我们再去理解property装饰器就不难了。我们会用property装饰器再重新MyClass类。\n",
    "\n",
    "class MyClass:\n",
    "    def __init__(self):\n",
    "        self._x = None\n",
    "        self._y = 1\n",
    "\n",
    "    @property\n",
    "    def x(self): # x是一个公有（可读可写）属性\n",
    "        \"\"\"\n",
    "        I'm the 'x' property.\n",
    "        \"\"\"\n",
    "        return self._x\n",
    "\n",
    "    @x.setter\n",
    "    def x(self, value):\n",
    "        self._x = value\n",
    "\n",
    "    @x.deleter\n",
    "    def x(self):\n",
    "        del self._x\n",
    "\n",
    "    @property\n",
    "    def y(self): # 只设置getter，y是一个私有（只读）属性\n",
    "        \"\"\"\n",
    "        I'm the 'y' property.\n",
    "        \"\"\"\n",
    "        return self._y\n",
    "@property装饰器会将x()方法转化为一个具有相同名称的只读属性的getter，并将x属性的文档字符串设置为x()方法的文档字符串。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
